package com.naiterui.ehp.bs.payment.controller;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.github.binarywang.wxpay.exception.WxPayException;
import com.naiterui.ehp.bp.support.exception.BaseExceptionCodes;
import com.naiterui.ehp.bp.support.exception.PublicService;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.utils.payment.bo.CloseOrderResultBO;
import com.naiterui.ehp.bp.utils.payment.bo.OrderStatusResultBO;
import com.naiterui.ehp.bp.utils.payment.bo.RefundRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderResultBO;
import com.naiterui.ehp.bs.payment.exception.NotifyRefundException;
import com.naiterui.ehp.bs.payment.exception.PaymentBusinessException;
import com.naiterui.ehp.bs.payment.service.IOrderService;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

/**
 * 支付功能相关HTTP接口：包括统一下单，查询订单，关闭订单，退款，回调通知接口
 * 具体接口请看接口注释
 *
 * @date 2017年09月09日
 * @since 1.0.0
 */
@Validated
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/order/")
@Api(value = "支付功能相关HTTP接口：包括统一下单，查询订单，关闭订单，退款", description = "支付功能相关HTTP接口：包括统一下单，查询订单，关闭订单，退款")
public class OrderController {

    private final IOrderService orderService;


    /**
     * 统一下单接口
     * 在发起支付前，需要调用统一下单接口，获取"预支付交易会话标识"
     * 如果此接口调用成功，那么将会将预支付信息存入MySQL，并且记录预支付生成的渠道,期间如果有人在调用，那么直接返回预支付信息，
     * 如果预支付信息过期，那么重新调用统一下单接口，并且将之前的预支付信息设置失效,
     * 如果三方接口不能使用的话，那么系统会根据内置的支付策略来切换支付渠道，这里必须保证order_pre_payment_info
     * 表中只能有一个可以使用的预支付信息，不能存在多个渠道可以使用的预支付信息，因为如果支付渠道是多个的话，无法确定此订单使用哪个渠道支付，后面的查询订单和退款功能将会出现使用问题
     *
     * @param request 请求对象，注意一些参数如notifyUrl等不用设置，方法内会自动从配置对象中获取到（前提是对应配置中已经设置）
     *
     * @return 返回预支付信息
     *
     * @date 2017年09月09日
     * @since 1.0.0
     */
    @PostMapping(value = "/pay")
    @ApiOperation(value = "统一下单接口")
    public ResponseVO<UnifiedOrderResultBO> pay(@Valid UnifiedOrderRequestBO request) throws WxPayException {
        return PublicService.returnResponseVO(this.orderService.unifiedOrder(request));
    }

    /**
     * 查询订单
     * 该接口提供所有微信支付订单的查询，商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。
     * 需要调用查询接口的情况：
     * ◆ 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知；
     * ◆ 调用支付接口后，返回系统错误或未知交易状态情况；
     * ◆ 调用被扫支付API，返回USERPAYING的状态；
     * ◆ 调用关单或撤销接口API之前，需确认支付状态；
     *
     * @param orderSn 商户系统内部的订单号 不能为空
     *
     * @return 支付状态
     *
     * @throws PaymentBusinessException 订单没查询到
     * @date 2017年09月09日
     * @since 1.0.0
     */
    @PostMapping("/query")
    @ApiOperation(value = "查询订单")
    @ApiImplicitParams({@ApiImplicitParam(name = "orderSn", value = "订单号", required = true, dataType = "string", paramType = "form")})
    public ResponseVO<OrderStatusResultBO> query(@NotEmpty(message = "订单号不能为空") String orderSn) throws PaymentBusinessException, WxPayException {
        log.info("查询订单接口 请求参数：orderSn = {}", orderSn);
        return PublicService.returnResponseVO(this.orderService.queryOrderPayStatus(orderSn));
    }

    /**
     * 关闭订单
     * 应用场景
     * 以下情况需要调用关单接口：
     * 1. 商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
     * 2. 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * 注意：订单生成后不能马上调用关单接口，最短调用时间间隔为5分钟。
     * 是否需要证书： 不需要。
     *
     * @param orderSn 商户系统内部的订单号 不能为空
     *
     * @throws PaymentBusinessException 订单没查询到
     * @date 2017年09月09日
     * @since 1.0.0
     */
    @PostMapping("/close")
    @ApiOperation(value = "关闭订单")
    @ApiImplicitParams({@ApiImplicitParam(name = "orderSn", value = "订单号", required = true, dataType = "string", paramType = "form")})
    public ResponseVO<CloseOrderResultBO> closeOrder(@NotEmpty(message = "订单号不能为空") String orderSn) throws PaymentBusinessException, WxPayException {
        this.orderService.closeOrder(orderSn);
        return PublicService.returnResponseVO(new CloseOrderResultBO(orderSn, 0));
    }

    /**
     * 申请退款
     *
     * @param request 退款信息
     *
     * @throws PaymentBusinessException 订单没查询到
     * @date 2017年09月09日
     * @since 1.0.0
     */
    @PostMapping("/refund")
    @ApiOperation(value = "申请退款")
    public ResponseVO<Void> refund(
            @Valid RefundRequestBO request) throws PaymentBusinessException, NotifyRefundException, WxPayException {
        String orderSn = request.getOrderSn();
        String refundOrderSn = request.getRefundOrderSn();
        log.info("调用退款接口 orderSn:{} refundOrderSn:{}", orderSn, refundOrderSn);
        this.orderService.refund(request);
        return PublicService.returnResponseVO(BaseExceptionCodes.SUCCESS);
    }

}
